package dk.sdu.mmmi.opn.assignment3easy.client;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

import dk.sdu.mmmi.opn.assignment3easy.acquaintance.ICatalogue;
import dk.sdu.mmmi.opn.assignment3easy.acquaintance.IEntry;
import dk.sdu.mmmi.opn.assignment3easy.acquaintance.Product;
import dk.sdu.mmmi.opn.assignment3easy.server.Server;
import dk.sdu.mmmi.opn.rmi.observer.IRemoteObservable;
import dk.sdu.mmmi.opn.rmi.observer.IRemoteObserver;
import dk.sdu.mmmi.opn.rmi.observer.RemoteObservable;

/**
 * Controller for the application
 * @author ups
 *
 */
@SuppressWarnings("serial")
public class ClientController extends UnicastRemoteObject implements IRemoteObserver {

	/**
	 * Singleton pattern instance
	 */
	private static ClientController instance;
	
	float value = 0;

	/**
	 * Singleton pattern accessor
	 */
	public static synchronized ClientController get() {
		if(instance==null) {
			try {
				instance = new ClientController();
			} catch(RemoteException e) {
				throw new Error("Unable to start client:"+e);
			}
		}
		return instance; 
	}

	/**
	 * The area in which text can be displayed
	 */
	private JTextPane displayArea;
	
	/**
	 * The label in which the current status can be displayed
	 */
	private JLabel statusLabel;
	
	/**
	 * The catalogue being manipulated
	 */
	private ICatalogue catalogue;
	
	/**
	 * Initialize, including connecting to a specific catalogue
	 */
	private ClientController() throws RemoteException { 
		String hostname = JOptionPane.showInputDialog(null, "Server hostname?", "localhost");
		int port = Server.PORT;
		try {
			catalogue = (ICatalogue)LocateRegistry.getRegistry(hostname, port).lookup(Server.NAME);
			catalogue.addObserver(this);
		} catch (RemoteException | NotBoundException e) {
			throw new Error("Connection failed: "+e);
		}
	}
	
	/**
	 * Increase button clicked in GUI
	 */
	public void increaseAction(String productName, String amountText) {
		changeEntry(productName,amountText,true);
	}

	/**
	 * Decrease button clicked in GUI
	 */
	public void decreaseAction(String productName, String amountText) {
		changeEntry(productName,amountText,false);
	}

	/**
	 * Helper: generic implementation of the increase/decrease action
	 */
	private void changeEntry(String productName, String amountText, boolean increase) {
		int amount = Integer.parseInt(amountText);
		new Thread(() ->{
			try {
				IEntry entry = catalogue.getEntry(productName);				
				if(entry==null) {
					setStatus("No such entry: "+productName);
					return;
				}
				if(entry.updateQuantity(increase ? amount : -amount)); // Returns true if success
				else
					setStatus("Negative stock not allowed");
			} catch(RemoteException exn) {
				throw new Error("Remote failure: "+exn);
			}
		}).start();
	}

	/**
	 * Show the status to the user 
	 */
	private void setStatus(String status) {
		if(statusLabel==null) throw new Error("Internal error: unable to display status");
		statusLabel.setText(status);
	}

	/**
	 * Update display, showing the current entries and their total value
	 */
	private synchronized void updateDisplay() {
		// Check that controller is properly initialized
		if(catalogue==null) throw new Error("Internal error: catalogue not set");
		if(displayArea==null) throw new Error("Internal error: display area not set");
		// Then do the display
		
		Document doc = displayArea.getDocument();
		// For each item in the stock, add it to the description list and the total value
		try {
			doc.remove(0, doc.getLength());
			catalogue.getEntryNames().parallelStream().forEach(name -> {
				try{
					IEntry entry = catalogue.getEntry(name);
					Product product = entry.getProduct();
					int quantity = entry.getQuantity();
					String text = product.getName()+"@"+product.getPrice()+": "+quantity+"\n";
					doc.insertString(doc.getLength(),text,null);
					value += quantity*product.getPrice();
				} catch(Exception e){}
			});
			doc.insertString(doc.getLength(),"Total value: "+value,null);
		
		} catch(RemoteException exn) {
			throw new Error("Remote failure: "+exn);
		} catch (BadLocationException e) {
			throw new Error("Internal error: bad text location in GUI");
		}
	}

	/**
	 * Search button clicked in GUI: use text area to show matching products
	 */
	public void searchAction(String prefix) {
		StringBuffer result = new StringBuffer();
		try {
			for(Product product: catalogue.search(prefix)) {
				result.append(product.getName()+"\n");
			}
			displayArea.setText(result.toString());
		} catch(RemoteException exn) {
			throw new Error("Remote failure: "+exn);
		}
	}

	/**
	 * Clear button clicked in GUI: go back to inventory display
	 */
	public void clearAction() {
		new Thread(() ->{
			try {
				catalogue.notifyObservers("ClearAction");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}).start();
	}
	
	/**
	 * Set the inventory display object
	 * (Note: high coupling, ideally use observer instead)
	 */
	public void setInventoryDisplay(JTextPane entriesDisplay) {
		this.displayArea = entriesDisplay;
		updateDisplay();
	}
	
	/**
	 * Set the status display object
	 * (Note: high coupling, ideally use observer instead)
	 */
	public void setStatusArea(JLabel label) {
		this.statusLabel = label;
		setStatus("Ready");
	}

	/**
	 * Remote notification that something changed on the server
	 */
	@Override
	public void update(IRemoteObservable target, Object event) throws RemoteException {
		this.updateDisplay();
	}

}
